/* AndyDroid - Android automation testing Framework.
 Copyright (C) 2011 MicroStrategy Test Automation Team

 AndyDroid is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with software. If not, contact us.
 
 @email:	robort1002@gmail.com
 */
package com.microstrategy.android.automation;

import java.lang.reflect.Constructor;
import java.util.ArrayList;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.ScrollView;
import android.widget.TextView;

import com.microstrategy.android.automation.tester.ViewTester;
import com.microstrategy.android.automation.tester.WebViewTester;
import com.microstrategy.android.automation.utils.ActivityUtils;
import com.microstrategy.android.automation.utils.DeviceUtils;
import com.microstrategy.android.automation.utils.ViewUtils;
import com.microstrategy.android.automation.utils.RClassUtils;
import com.microstrategy.android.automation.tester.ViewGroupTester;

/**
 * Andy Android Test Framework Class for AndyTestCase to include.
 * 
 * 
 * 
 */
class Andy {

	/**
	 * Log tag.
	 */
	private static final String LOG_TAG = "Andy";

	/**
	 * Parameter of Andy.
	 */
	private Instrumentation inst;
	private ActivityUtils activityUtils;
	private DeviceUtils AndyDevice;

	/**
	 * Max time to find a view.
	 */
	private int maxTimeToFindView;

	private static final String LANDSCAPE = "landscape";

	private static final String PORTRAIT = "portrait";
	
	/**
	 * Constructor of {@link Andy}.
	 * 
	 * @param inst
	 *            {@link Instrumentation}.
	 * @param activity
	 *            {@link Activity}.
	 */
	Andy(Instrumentation inst, Activity activity) {
		this.inst = inst;
		this.activityUtils = new ActivityUtils(inst, activity);
		this.AndyDevice = DeviceUtils.getInstance(inst, activity);
	}
	
	DeviceUtils getDevice() {
		return AndyDevice;
	}
	
	/**
	 * Return an instance of {@code AndyView} or its subclass by the given name
	 * and return type.
	 * 
	 * @param <T>
	 *            The {@code AndyView} or its subclass.
	 * @param name
	 *            String name of view id, the string after @+id/ defined in
	 *            layout files.
	 * @param returnType
	 *            The {@code AndyView} or its subclass, this parameter
	 *            determines the return type of this method.
	 * @return The {@code AndyView} or its subclass.
	 * @throws Exception
	 */
	<T extends ViewTester> T findAndyViewById(String name, Class<T> returnType)
			throws Exception {
		Constructor<?>[] constructors = returnType.getDeclaredConstructors();
		View view = findViewById(name);
		if (null == view) {
			Logger.e(LOG_TAG, "findAndyViewById(" + name + ") return null");
			return null;
		}
		Object obj = constructors[0].newInstance(inst, view,
				getCurrentActivity());
		return returnType.cast(obj);
	}

	<T extends ViewTester> T findAndyViewById(int id, Class<T> returnType)
			throws Exception {
		Constructor<?>[] constructors = returnType.getDeclaredConstructors();
		View view = findViewById(id);
		if (null == view) {
			Logger.e(LOG_TAG, "findAndyViewById(" + id + ") return null");
			return null;
		}
		
		Object obj = constructors[0].newInstance(inst, view,
				getCurrentActivity());
		return returnType.cast(obj);
	}

	private View findViewById(int id, int timeout) {
		final long startTime = System.currentTimeMillis();
		Logger.i(LOG_TAG, "maxTimeToFindView is " + timeout);

		while (System.currentTimeMillis() < startTime + timeout) {

			updateActivities();

			ArrayList<View> all = ViewUtils.getAllViews(false);

			for (View view : all) {
				View result = view.findViewById(id);
				if (null != result) {
					Log.i(LOG_TAG, "findViewById(" + id + ") succeed");
					ViewUtils.scrollInToScreenIfNeeded(inst, getCurrentActivity(), result);
					return result;
				}
			}
		}
		Logger.e(LOG_TAG, "findViewById(" + id + ") return null",
				new Throwable());
		return null;
	}

	private View findViewById(int id) {
		return findViewById(id, getMaxTimeToFindView());
	}

	private View findViewById(String name, int timeout) throws Exception {
		int id = RClassUtils.getIdByName(getPackageName(), name);
		return findViewById(id, timeout);
	}

	private View findViewById(String name) throws Exception {
		return findViewById(name, getMaxTimeToFindView());
	}

	/**
	 * Return an instance of {@code AndyView} or its subclass by the id tree and
	 * return type. This method is used to find a view that is contained in
	 * another view.
	 * 
	 * @param <T>
	 *            The {@code AndyView} or its subclass.
	 * @param idTree
	 *            Parent id plus child id with ">", but without whitespace.
	 *            Example: parentId>childId.
	 * @param returnType
	 *            The {@code AndyView} or its subclass, this parameter
	 *            determines the return type of this method.
	 * @return The {@code AndyView} or its subclass.
	 * @throws Exception
	 */
	<T extends ViewTester> T findAndyViewInTree(String idTree, Class<T> returnType)
			throws Exception {
		ViewTester AndyView = null;
		String[] ids = idTree.split(">");

		if (ids.length == 1) {
			AndyView = findAndyViewById(ids[0], returnType);
			
		} else if (ids.length > 1) {
			AndyView = findAndyViewById(ids[0], ViewGroupTester.class);
			for (int i = 1; i < ids.length; i++) {
				AndyView = ((ViewGroupTester) AndyView).findAndyViewById(ids[i], returnType);
			}
		}
		return returnType.cast(AndyView);
	}
	
	/**
	 * Return an instance of {@code AndyTextView} or its subclass by the given
	 * text.
	 * 
	 * @param <T>
	 *            The {@code AndyTextView} or its subclass.
	 * @param text
	 *            Text to be found.
	 * @param returnType
	 *            The {@code AndyTextView} or its subclass, this parameter
	 *            determines the return type of this method.
	 * @return The {@code AndyTextView} or its subclass.
	 * @throws Exception
	 */
/*	<T extends AndyTextView> T findAndyTextViewByText(String text,
			Class<T> returnType) throws Exception {
		Constructor<?>[] constructors = returnType.getDeclaredConstructors();
		View view = findViewByText(text);
		if (null == view) {
			Logger.e(LOG_TAG, "findAndyTextViewByText(" + text
					+ ") return null");
			return null;
		}
		Object obj = constructors[0].newInstance(inst, view,
				getCurrentActivity());
		return returnType.cast(obj);
	}*/

	private TextView findViewByText(String text, int timeout) {
		final long startTime = System.currentTimeMillis();
		Logger.i(LOG_TAG, "maxTimeToFindView is " + timeout);

		while (System.currentTimeMillis() < startTime + timeout) {

			updateActivities();

			ArrayList<TextView> textViews = getAllTextView();

			for (TextView textView : textViews) {
				String current = textView.getText().toString();
				if (current.contains(text)) {
					Log.i(LOG_TAG, "findViewByText(" + text + ") succeed. In: " + current);
					ViewUtils.scrollInToScreenIfNeeded(inst, getCurrentActivity(), textView);
					return textView;
				}
			}
		}
		Logger.e(LOG_TAG, "findViewByText(" + text + ") return null",
				new Throwable());
		return null;
	}

	private ArrayList<TextView> getAllTextView() {
		ArrayList<View> all = ViewUtils.getAllViews(false);
		ArrayList<TextView> textViews = ViewUtils.filterViews(TextView.class,
				all);
		return textViews;
	}

	private TextView findViewByText(String text) {
		return findViewByText(text, getMaxTimeToFindView());
	}

	WebViewTester findAndyWebViewById(String id) throws Exception {
		Constructor<?>[] constructors = WebViewTester.class
				.getDeclaredConstructors();
		View view = findViewById(id);
		
		if (null == view) {
			Logger.e(LOG_TAG, "findAndyWebViewById(" + id + ") return null");
			return null;
		}
		
		ViewUtils.scrollInToScreenIfNeeded(inst, getCurrentActivity(), view);
		WebView webView = WebView.class.cast(view);
		Object obj = constructors[0].newInstance(inst, webView);
		return WebViewTester.class.cast(obj);
	}
    
	/**
	 * @deprecated
	 * @param text
	 * @param timeout
	 * @return
	 */
	boolean waitForText(String text, int timeout) {
		TextView textView = findViewByText(text, timeout);
		return (null != textView) ? true : false;
	}

/*	AndyToast findAndyToast(String message) throws Exception {
		final long startTime = System.currentTimeMillis();
		int id = RClassUtils.getIdByName("com.android.internal", "message");

		while (System.currentTimeMillis() < startTime + getMaxTimeToFindView()) {
			ArrayList<TextView> textViews = getAllTextView();
			for (TextView textView : textViews) {
				String content = textView.getText().toString();
				Logger.i(LOG_TAG, "Text content is " + content);
				if (id == textView.getId() && content.contains(message)) {
					return new AndyToast(content);
				}
			}
		}
		return null;
	}*/

	<T extends WebViewTester> T findAndyWebViewById(String name,
			Class<T> returnType) throws Exception {
		Constructor<?>[] constructors = returnType.getDeclaredConstructors();
		View view = findViewById(name);
		if (null == view) {
			return null;
		}
		Object obj = constructors[0].newInstance(inst, view);
		return returnType.cast(obj);
	}

	/**
	 * Return an instance of {@code AndyAbsListView} or its subclass by the
	 * index.
	 * 
	 * @param <T>
	 *            The {@code AndyAbsListView} or its subclass.
	 * @param index
	 *            AbsListView's index in current {@code Activity}, in most of
	 *            the time, it is 0.
	 * @param returnType
	 *            The {@code AndyAbsListView} or its subclass, this parameter
	 *            determines the return type of this method.
	 * @return The {@code AndyAbsListView} or its subclass.
	 * @throws Exception
	 */
/*	<T extends AndyAbsListView> T findListByIndex(int index, Class<T> returnType)
			throws Exception {
		return findViewByIndex(index, AbsListView.class, returnType);
	}*/

	/**
	 * Return an instance of {@code AndyScrollView}by the index.
	 * 
	 * @param index
	 *            ScrollView's index in current {@code Activity}, in most of the
	 *            time, it is 0.
	 * @return The {@code AndyScrollView}.
	 * @throws Exception
	 */
/*	AndyScrollView findScrollViewByIndex(int index) throws Exception {
		return findViewByIndex(index, ScrollView.class, AndyScrollView.class);
	}*/

	<T extends ViewTester> T findAndyViewByIndex(int index,
			Class<? extends View> view, Class<T> returnType) throws Exception {
		return findViewByIndex(index, view, returnType);
	}

	private <T extends View> ArrayList<T> findViewByIndex(Class<T> view) {
		inst.waitForIdleSync();
		try {
			Thread.sleep(getMaxTimeToFindView());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		ArrayList<View> all = ViewUtils.getAllViews(false);
		updateActivities();
		all = ViewUtils.removeInvisibleViews(all);
		ArrayList<T> lists = ViewUtils.filterViews(view, all);
		return lists;
	}

	private <T extends ViewTester> T findViewByIndex(int index,
			Class<? extends View> view, Class<T> returnType) throws Exception {
		Constructor<?>[] constructors = returnType.getDeclaredConstructors();
		ArrayList<? extends View> viewList = findViewByIndex(view);

		if (0 == viewList.size()) {
			Logger.e(LOG_TAG, "findViewByIndex(" + index + ") return null");
			return null;
		}
		
		View listItem = viewList.get(index);
		ViewUtils.scrollInToScreenIfNeeded(inst, getCurrentActivity(), listItem);

		Object obj = constructors[0].newInstance(inst, listItem,
				getCurrentActivity());
		return returnType.cast(obj);
	}
    
	/**
	 * @deprecated
	 * @param orientation
	 */
	void setScreenOrientation(int orientation) {
		activityUtils.setActivityOrientation(orientation);
	}
    
	/**
	 * @deprecated
	 * @return
	 */
	String getScreenOrientation() {
		inst.waitForIdleSync();
		int orientation = getCurrentActivity().getRequestedOrientation();
		return (ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE == orientation) ? LANDSCAPE
				: PORTRAIT;
	}
    
	/**
	 * @deprecated
	 * @param name
	 * @param timeout
	 * @return
	 */
	boolean waitForActivity(String name, int timeout) {
		return activityUtils.waitForActivity(name, timeout);
	}
    
	/**
	 * @deprecated
	 * @param name
	 */
	void goBackToActivity(String name) {
		activityUtils.goBackToActivity(name);
	}

	/**
	 * Get current Activity.
	 * 
	 * @return Current Activity
	 */
	private Activity getCurrentActivity() {
		return activityUtils.getCurrentActivity();
	}

	private String getPackageName() {
		return inst.getTargetContext().getPackageName();
	}

	/**
	 * Set max time to find view, default time is 5000ms. If a view can not be
	 * found in this time, the test will fail.
	 * 
	 * @param maxTime
	 *            Time in milliseconds.
	 */
	void setMaxTimeToFindView(int maxTime) {
		this.maxTimeToFindView = maxTime;
	}

	private int getMaxTimeToFindView() {
		return maxTimeToFindView;
	}

	private void updateActivities() {
		activityUtils.updateActivities(getCurrentActivity());
	}

	private Resources getResource() {
		return inst.getTargetContext().getResources();
	}

	String getStringById(String name) throws Exception {
		int id = RClassUtils.getRFieldByName(getPackageName(), "string", name);
		return getResource().getString(id);
	}

	XmlResourceParser getLayoutById(String name) throws Exception {
		int id = RClassUtils.getRFieldByName(getPackageName(), "layout", name);
		return getResource().getLayout(id);
	}

	protected void finalize() {
		try {
			activityUtils.finalize();
			super.finalize();

		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
}
